package sk.upjs.ics.fca.concepts;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

/**
 * Trieda reprezentuje kontext - tabulka splnajuca objektovo-atributovy model a
 * zahrna vsetky suvisiace metody pouzivane kontextom
 * 
 * @author Patrik Mihalcin
 */
public class Context {

	private int attributesCount = 0;
	private int objectsCount = 0;
	private Logic logic;
	private Double[][] relation;

	private Set<Concept> concepts = new HashSet<Concept>();

	private Map<List<Double>, List<List<Double>>> upperNeighbors = new HashMap<List<Double>, List<List<Double>>>();
	private Map<List<Double>, List<List<Double>>> lowerNeighbors = new HashMap<List<Double>, List<List<Double>>>();

	private List<List<Double>> extents = new ArrayList<List<Double>>();
	private List<List<Double>> intents = new ArrayList<List<Double>>();

	/**
	 * Konstruktor akceptuje ako vstup stupen fuzzy logiky a relaciu (2D pole
	 * hodnot)
	 * 
	 * @param logicDegree
	 *            stupen fuzzy logiky
	 * @param relation
	 *            relacia - 2D pole
	 */
	public Context(Integer logicDegree, Double[][] relation) {
		this.logic = new Logic(logicDegree);
		this.relation = relation;

		objectsCount = relation.length;
		attributesCount = relation[0].length;
	}

	/**
	 * Vrati string reprezentaciu kontextu
	 */
	@Override
	public String toString() {

		String result = "";

		for (int i = 0; i < relation.length; i++) {
			for (int j = 0; j < relation[0].length; j++) {
				result += relation[i][j] + " ";
			}
			result += "\n";
		}

		return result;
	}

	/**
	 * Konstruktor akceptuje ako vstup stupen fuzzy logiky a relaciu (zoznam
	 * zoznamov hodnot)
	 * 
	 * @param logicDegree
	 *            stupen fuzzy logiky
	 * @param relation
	 *            relacia - zoznam zoznamov
	 */
	public Context(Integer logicDegree, List<List<Double>> relation) {
		this.logic = new Logic(logicDegree);

		objectsCount = relation.size();
		attributesCount = relation.get(0).size();

		Double[][] array = new Double[objectsCount][attributesCount];

		for (int i = 0; i < objectsCount; i++) {
			for (int j = 0; j < attributesCount; j++) {
				array[i][j] = relation.get(i).get(j);
			}
		}

		this.relation = array;
	}

	// context operators
	/**
	 * Fuzzy mnozinu atributov pretransformuje na fuzzy mnozinu objektov podla
	 * definice
	 * 
	 * @param b
	 *            fuzzy mnozina objektov
	 * @return fuzzy mnozina atributov
	 */
	public List<Double> down(List<Double> b) {
		// intent
		if (!(attributesCount == b.size())) {
			throw new ContextOperatorException(attributesCount, b.size());
		}
		List<Double> a = new ArrayList<Double>();
		for (int bb = 0; bb < objectsCount; bb++) {
			Double min = Double.MAX_VALUE;
			for (int aa = 0; aa < attributesCount; aa++) {
				Double value = null;
				try {
					value = logic.implication(b.get(aa), relation[bb][aa]);
				} catch (IndexOutOfBoundsException e) {
					throw new ContextOperatorException(attributesCount,
							b.size());
				}
				min = logic.infimum(value, min);
			}
			a.add(min);
		}
		return a;
	}

	/**
	 * Fuzzy mnozinu objektov pretransformuje na fuzzy mnozinu atributov podla
	 * definice
	 * 
	 * @param a
	 *            fuzzy mnozina objektov
	 * @return fuzzy mnozina atributov
	 */
	public List<Double> up(List<Double> a) {
		// extent
		if (!(objectsCount == a.size())) {
			throw new ContextOperatorException(objectsCount, a.size());
		}
		List<Double> b = new ArrayList<Double>();
		for (int aa = 0; aa < attributesCount; aa++) {
			Double min = Double.MAX_VALUE;
			for (int bb = 0; bb < objectsCount; bb++) {
				Double value = null;
				try {
					value = logic.implication(a.get(bb), relation[bb][aa]);
				} catch (IndexOutOfBoundsException e) {
					throw new ContextOperatorException(objectsCount, b.size());
				}
				min = logic.infimum(value, min);
			}
			b.add(min);
		}
		return b;
	}

	/**
	 * Vytvori uzaver fuzzy mnoziny
	 * 
	 * @param set
	 *            fuzzy mnozina
	 * @param closureType
	 *            typ uzaveru
	 * @return uzaver mnoziny
	 */
	public List<Double> closure(List<Double> set, ClosureType closureType) {
		if (closureType == ClosureType.DOWN_UP) {
			return up(down(set));
		}
		return down(up(set));
	}

	private List<Double> generatorOfUpperNeighbors(Integer index,
			List<Double> set, ClosureType closureType) {
		// index in fuzzy set b, where truth value will be replaced with next
		// higher truth value
		List<Double> result = new ArrayList<Double>(set);
		ListIterator<Double> it = result.listIterator();

		// find out where next truth value should be set using ListIterator
		index++;
		Double value = null;
		while (index-- > 0) {
			value = it.next();
		}

		// set next higher truth value
		it.set(logic.getNextTruthValue(value));

		// do closure
		return closure(result, closureType);
	}

	private List<Attribute> minimalAttributeSet(List<Double> b) {
		// return list of attributes which has value different from 1.0
		List<Attribute> result = new ArrayList<Attribute>();
		int y = 0;
		for (ListIterator<Double> it = b.listIterator(); it.hasNext();) {
			if (!it.next().equals(1.0)) {
				result.add(new Attribute(y, b.get(y)));
			}
			y++;
		}
		return result;
	}

	private List<Objekt> minimalObjectSet(List<Double> a) {
		// return list of objects which has value different from 1.0
		List<Objekt> result = new ArrayList<Objekt>();
		int x = 0;
		for (ListIterator<Double> it = a.listIterator(); it.hasNext();) {
			if (!it.next().equals(1.0)) {
				result.add(new Objekt(x, a.get(x)));
			}
			x++;
		}
		return result;
	}

	private List<Attribute> increasedAttributes(int y, List<Double> b,
			List<Double> d) {
		// return list of increased attributes (apart from attribute y)
		// comparing 2 fuzzy sets
		List<Attribute> result = new ArrayList<Attribute>();
		for (int z = 0; z < b.size(); z++) {
			if (z != y) {
				if (b.get(z) < d.get(z)) {
					result.add(new Attribute(z, b.get(z)));
				}
			}
		}
		return result;
	}

	private List<Objekt> increasedObjects(int x, List<Double> a, List<Double> d) {
		// return list of increased objects (apart from object x) comparing 2
		// fuzzy sets
		List<Objekt> result = new ArrayList<Objekt>();
		for (int z = 0; z < a.size(); z++) {
			if (z != x) {
				if (a.get(z) < d.get(z)) {
					result.add(new Objekt(z, a.get(z)));
				}
			}
		}
		return result;
	}

	private List<Attribute> intersectionAttributes(List<Attribute> min,
			List<Attribute> increased) {
		List<Attribute> result = new ArrayList<Attribute>();
		for (Attribute atribut1 : increased) {
			for (Attribute atribut2 : min) {
				if (atribut1.compareTo(atribut2) == 1) {
					result.add(atribut1);
				}
			}
		}
		return result;
	}

	private List<Objekt> intersectionObjects(List<Objekt> min,
			List<Objekt> increased) {
		List<Objekt> result = new ArrayList<Objekt>();
		for (Objekt objekt1 : increased) {
			for (Objekt objekt2 : min) {
				if (objekt1.compareTo(objekt2) == 1) {
					result.add(objekt1);
				}
			}
		}
		return result;
	}

	private List<List<Double>> neighbors(List<Double> set,
			ClosureType closureType) {
		// compute upper neigbors for each newly added fixpoint and update the
		// info about lower neigbors
		List<List<Double>> result = new ArrayList<List<Double>>();

		if (closureType == ClosureType.DOWN_UP) {
			List<Attribute> min = minimalAttributeSet(set);
			for (Iterator<Attribute> it = min.iterator(); it.hasNext();) {
				Attribute y = it.next();
				List<Double> d = generatorOfUpperNeighbors(y.getIndex(), set,
						closureType);
				List<Attribute> increased = increasedAttributes(y.getIndex(),
						set, d);
				if (intersectionAttributes(min, increased).isEmpty()) {
					result.add(d);
				} else {
					it.remove();
				}
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			List<Objekt> min = minimalObjectSet(set);
			for (Iterator<Objekt> it = min.iterator(); it.hasNext();) {
				Objekt x = it.next();
				List<Double> d = generatorOfUpperNeighbors(x.getIndex(), set,
						closureType);
				List<Objekt> increased = increasedObjects(x.getIndex(), set, d);
				if (intersectionObjects(min, increased).isEmpty()) {
					result.add(d);
				} else {
					it.remove();
				}
			}
		}

		return result;
	}

	private void generateFrom(List<Double> set, List<List<Double>> f,
			ClosureType closureType) {
		// generates fixpoints
		List<List<Double>> neighbors = neighbors(set, closureType);

		// be careful when you are adding lists to arraylist in Map
		// workaround using for
		// update upperNeighbors
		upperNeighbors.put(set, new ArrayList<List<Double>>());
		for (List<Double> neighbor : neighbors) {
			upperNeighbors.get(set).add(neighbor);
		}

		for (List<Double> d : neighbors) {
			if (!lowerNeighbors.keySet().contains(d)) {
				lowerNeighbors.put(d, new ArrayList<List<Double>>());
			}
			lowerNeighbors.get(d).add(set);
		}
		neighbors.removeAll(f);

		for (List<Double> d : neighbors) {
			if (neighbors.contains(d)) {
				f.add(d);
			}
		}
		for (List<Double> d : neighbors) {
			generateFrom(d, f, closureType);
		}
	}

	/**
	 * Nastartuje generovanie zvazu konceptov. Algoritmus je popisany v praci -
	 * kapitola 2.
	 * 
	 * @param closureType
	 *            typ uzaveroveho operatora
	 */
	public void lattice(ClosureType closureType) {
		concepts.clear();
		upperNeighbors.clear();
		lowerNeighbors.clear();
		extents.clear();
		intents.clear();

		List<List<Double>> f = new ArrayList<List<Double>>();
		List<Double> emptySet = new ArrayList<Double>();

		// we start with the least fixpoint of fuzzy closure operator, which is
		// closure of empty set
		if (closureType == ClosureType.DOWN_UP) {
			for (int i = 0; i < attributesCount; i++) {
				emptySet.add(0.0);
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			for (int i = 0; i < objectsCount; i++) {
				emptySet.add(0.0);
			}
		}
		// do closure
		List<Double> set = closure(emptySet, closureType);
		// closure of empty set has no lower neighbor
		lowerNeighbors.put(set, new ArrayList<List<Double>>());

		f.add(set);
		generateFrom(set, f, closureType);

		if (closureType == ClosureType.DOWN_UP) {
			for (List<Double> intent : f) {
				concepts.add(new Concept(down(intent), intent));
				intents.add(intent);
				extents.add(down(intent));
			}
		} else if (closureType == ClosureType.UP_DOWN) {
			for (List<Double> extent : f) {
				concepts.add(new Concept(extent, up(extent)));
				extents.add(extent);
				intents.add(up(extent));
			}
		}
	}

	/**
	 * Vrati mnozinu konceptov
	 * 
	 * @return mnozina konceptov
	 */
	public Set<Concept> getConcepts() {
		return concepts;
	}

	/**
	 * Vrati mapu, kde ako kluc je koncept a ako hodnotu mnozinu jeho dolnych
	 * susedov
	 * 
	 * @return koncepty a ich dolni susedia
	 */
	public Map<List<Double>, List<List<Double>>> getLowerNeighbors() {
		return lowerNeighbors;
	}

	/**
	 * Vrati mapu, kde ako kluc je koncept a ako hodnotu mnozinu jeho hornych
	 * susedov
	 * 
	 * @return koncepty a ich horni susedia
	 */
	public Map<List<Double>, List<List<Double>>> getUpperNeighbors() {
		return upperNeighbors;
	}

	/**
	 * Vrati mnozinu vsetkych extentov
	 * 
	 * @return mnozina vsetkych extentov
	 */
	public List<List<Double>> getExtents() {
		return extents;
	}

	/**
	 * Vrati mnozinu vsetkych intentov
	 * 
	 * @return mnozina vsetkych intentov
	 */
	public List<List<Double>> getIntents() {
		return intents;
	}

	/**
	 * Vrati pocet atributov kontextu
	 * 
	 * @return pocet atributov
	 */
	public int getAttributesCount() {
		return attributesCount;
	}

	/**
	 * Vrati pocet objektov kontextu
	 * 
	 * @return pocet objektov
	 */
	public int getObjectsCount() {
		return objectsCount;
	}

	/**
	 * Vrati relaciu
	 * 
	 * @return relacia
	 */
	public Double[][] getRelation() {
		return relation;
	}

	/**
	 * Vrati pouzivanu logiku
	 * 
	 * @return logika
	 */
	public Logic getLogic() {
		return logic;
	}
}